#!/usr/local/bin/ruby
#
#  Copyright (c) 2009 Mu Dynamics, Inc. All rights reserved.

require 'rubygems'
require 'net/https'
require "xml/libxml"
require 'uri'

include LibXML

$DEBUG = true

class DdtApi
  attr_accessor :host, :docroot, :cookie, :username, :password, :posted_uuid, :testsuite, :results, :failed, :errors
  def initialize(host=ENV["MU_IP"], docroot = "/ddt/rest")
    @host = host
    @username  = ENV['MU_ADMIN_USER'] || "admin"
    @password  = ENV['MU_ADMIN_PASS'] || "admin" 
    @docroot = docroot
    @cookie = ''
    @testsuite = ''
   # request "newTestSession"
  end

  def new_session
    return request("newTestSession")
  end

  def testbed(uuid)
    return get("setTestbed/uuid/#{uuid}")
  end

  def target(uuid)
    return get("setTarget/uuid/#{uuid}")
  end

  def setup_session
    return get("setupTestSession")
  end

  def scenario(uuid)
    return get("loadScenario/uuid/#{uuid}")
  end

  def result
    return request("nextTestcaseResult")
  end

  def collect_results(timeout=60)
    start_time = Time.new
    the_end = false
    id = 0
    @results = Hash.new { |hash, key| hash[key] = Hash.new }
    @failed = Hash.new
    @errors = Array.new

    # send the nextTestCaseResults message until
    # the "END" is reported back. Then we know we're done
    while !the_end do
      sleep 5
      doc = request("nextTestcaseResults")
      $DEBUG and puts doc
      return false if doc == "Error status code 500"
      return false if doc.find('//response').nil?

      # parse out the response element of the request response     
      doc.find('//response').each do |t|

        # the name_index field is formed by joining the name and index elements
        # the name_index is used as the key of the results hashmap
        name_index = t['name'] + "." + t['index']
        id += 1
        @results[name_index]['id'] = id

        # the special value "END" indicates that the results are complete
        if t['name'] == "END"
          the_end = true
        else
          # add each result to the results hash
          # the 'options' are a special case - they are put into their own hashmap before
          # being added to the results hash
          options = Hash.new
          t.each_element do |opt|
            next unless opt.name == "options"
            name = opt['name'].gsub(/scenario_user_options\./,'')
            options[name] = opt['value']
          end
          @results[name_index]['options'] = options
          @results[name_index]['passed'] = t['passed']
          # if the test failed, a message element is included in the results
          if t.attributes['passed'] == "false"
            @failed[name_index] = t.find_first('//message').content
            @results[name_index]['message'] = t.find_first('//message').content
          end
        end
      end
      unless doc.find_first('//result/@error').nil?
        @errors << doc.find_first('//result')['error']
      end
      return "timed out" if (Time.new - start_time) > timeout
    end
  end

  def teardown_session
    return get("tearDownTestSession")
  end

  def run(uuid=@testsuite)
    get("runTestSuite/uuid/#{uuid}")
  end

  def eval(uuid=@testsuite)
    return get("runTestSuite/uuid/#{uuid}")
  end

  def csv_export(uuid=@testsuite)
    return get("exportCSV/uuid/#{uuid}")
  end

  def csv_import(csv)
    resp = post_csv(csv,"/ddt/rest/importCSV")
    return resp.find_first('//response').content
  end

  def csv_run(csv)
    return post_csv(csv,"/ddt/rest/runCSV")
  end

  def csv_eval(csv)
    return post_csv(csv,"/ddt/rest/runCSVEval")
  end
 
  # get will return either the response or error
  def get(doc)
    result = request(doc)
    unless result.find_first('//*[@error]').nil?
      return result.find_first('//*[@error]')['error']
    end
    unless result.find_first('//error').nil?
      return result.find_first('//error').content
    end
    unless result.find_first('//response').nil?
      return result.find_first('//response').content
    end
    $DEBUG and puts "GET: SERVER ERROR - #{result}"
    return false
  end

  # send request to api
  def request(doc)
    uri = URI.parse("https://#{@host}/")
    resp = response = "";
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = true
    escaped = URI.escape(doc)
    http.start do |http|
      req = Net::HTTP::Get.new("#{@docroot}/#{escaped}", {"User-Agent" => @username})
      req.basic_auth(@username, @password)
      @cookie.empty? or req['Cookie'] = @cookie
      response = http.request(req)
      $DEBUG and puts "RESPONSE: #{response.body}"
      response['Set-Cookie'].nil? or @cookie = response['Set-Cookie']
      if response.code != "200"
        $DEBUG and puts "Error status code #{response.code}"
        return "Error status code #{response.code}"
      end
      resp = response.body
    end
    @log and @log.write resp
    if (/<.+>/).match(resp)
       xmldoc = XML::Document.string(resp)
    else
       xmldoc = XML::Document.new
       xmldoc.root = XML::Node.new('err_node', resp)
    end
    return xmldoc
  end

  # Post the File contents to the muserver.
  # Default url is template import
  def post_template(filepath,url="/api/v3/templates/import")
    doc = File.read(filepath)
    xmldoc = nil
    uri = URI.parse("https://#{@host}/")
    resp = response = "";
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = true
    http.start do |http|
      req = Net::HTTP::Post.new(url, {"User-Agent" => @username})
      req.basic_auth(@username, @password)
      @cookie.empty? or req['Cookie'] = @cookie
      req.body = doc.to_s
      response = http.request(req)
      $DEBUG and puts "RESPONSE: #{response.body}"
      if response.code != "200"
        $DEBUG and puts "Error status code #{response.code}"
        return "Error status code #{response.code}"
      end
      resp = response.body
    end
    @log and @log.write resp
     #  XML Document
     if (/<.+>/).match(resp)
       xmldoc = XML::Document.string(resp)
     else
       xmldoc = XML::Document.new
       xmldoc.root = XML::Node.new('err_node', resp)
     end
    @posted_uuid = xmldoc.find_first('//uuid').content
  end

  # post a test suite csv file
  def post_csv(data,url)
    xmldoc = nil
    uri = URI.parse("https://#{@host}/")
    resp = response = "";
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = true
    http.start do |http|
      req = Net::HTTP::Post.new(url, {"User-Agent" => @username})
      req.basic_auth(@username, @password)
      @cookie.empty? or req['Cookie'] = @cookie
      req.set_form_data({'csv' => data})
      response = http.request(req)
      $DEBUG and puts "RESPONSE: #{response.body}"
      if response.code != "200"
        $DEBUG and puts "Error status code #{response.code}"
        return "Error status code #{response.code}"
      end
      resp = response.body
    end
    @log and @log.write resp
    if (/<.+>/).match(resp)
      xmldoc = XML::Document.string(resp)
    else
      xmldoc = XML::Document.new
      xmldoc.root = XML::Node.new('err_node', resp)
    end
    return xmldoc
  end

  # parse through the results collection
  # expanding the inner 'options' hashtable along with
  # the 'passed' and 'id' results
  def display_results
    unless @results.nil?
      $DEBUG and puts "Tests:\n" unless @results.size == 0
      @results.each do | k, v |
       name_index = k
       $DEBUG and puts "#{k}"
       v.each do | k2, v2 |
         if k2 == "options"
           $DEBUG and puts "  options:"
           v2.each do | k3, v3 |
             $DEBUG and puts "    #{k3} = #{v3}"
           end
         else
           $DEBUG and puts "  #{k2} = #{v2}"
           if k2 == "passed" and v2 == "false"
              $DEBUG and puts "    message: #{@failed[name_index]}"
           end
         end
       end
       $DEBUG and puts "\n"
      end
    end

    unless @errors.nil?
      $DEBUG and puts "Errors:\n" unless @errors.size == 0
      @errors.each do | e |
         $DEBUG and puts e
      end
    end
  end

end
